﻿namespace ModuleOne
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.IO;
    using System.Linq;
    using System.Windows.Threading;

    using MEF_Test.Bricks;
    using MEF_Test.Bricks.TemplateProvider;
    using MEF_Test.Contracts;

    public class DiskMediaBrowsingProvider : MediaBrowsingProviderBase
    {
        #region Constants and Fields

        private readonly IDiskBrowserNodeViewModelFactory _browserNodeViewModelFactory;

        private readonly IDiskFileHandlerFactory _fileHandlerFactory;

        /// <summary>
        /// Monitors the current directory (the Path property must be kept up-to-date in order for this to work. see the GoToNode() method)
        /// </summary>
        private readonly FileSystemWatcher _fileSystemWatcher;

        private readonly IMediaHandlerFactoryProvider _mediaHandlerFactoryProvider;
        private readonly ITemplateProvider _templateProvider;

        #endregion

        // TODO : move it to superclass using a less specific factory interface.

        #region Constructors and Destructors

        public DiskMediaBrowsingProvider(IBrowserNodeViewModel<IDiskFileHandler> defaultNode, IDiskFileHandlerFactory fileHandlerFactory, IMediaHandlerFactoryProvider mediaHandlerFactory, ITemplateProvider templateProvider, IDiskBrowserNodeViewModelFactory browserNodeViewModelFactory)
        {
            if (fileHandlerFactory == null)
            {
                throw new ArgumentNullException("fileHandlerFactory");
            }

            if (mediaHandlerFactory == null)
            {
                throw new ArgumentNullException("mediaHandlerFactory");
            }

            if (templateProvider == null)
            {
                throw new ArgumentNullException("templateProvider");
            }

            if (browserNodeViewModelFactory == null)
            {
                throw new ArgumentNullException("browserNodeViewModelFactory");
            }

            _fileHandlerFactory = fileHandlerFactory;
            _browserNodeViewModelFactory = browserNodeViewModelFactory;
            _templateProvider = templateProvider;
            _mediaHandlerFactoryProvider = mediaHandlerFactory;

            // here, we don't need to notify the property change, since it's done before the rendering of the View, and therefore, there was no previous value.
            // the first value ever will be defaultNode !
        
            _fileSystemWatcher = new FileSystemWatcher
                             {
                                 IncludeSubdirectories = false,
                             };
            
            this.GoToNode(defaultNode);

            _fileSystemWatcher.EnableRaisingEvents = true;

            // the filesystemwatcher needs to have a Path set before the EnableRaisingEvents property is set to true !
            _fileSystemWatcher.Deleted += fileSystemWatcherDeleted;
            _fileSystemWatcher.Created += fileSystemWatcherCreated;            
        }

        #endregion

        #region Properties

        public override bool IsRoot
        {
            get
            {
                var diskBrowerNode = _currentElement as IBrowserNodeViewModel<IDiskFileHandler>;
                if (diskBrowerNode == null)
                {
                    var message = string.Format("New node of type {0} is not of the right type : {1} expected", this._currentElement.GetType().Name, typeof(BrowserNodeViewModel<IDiskFileHandler>).Name);
                    throw new ArgumentException(message);
                }

                // if the containing directory of the current element ( assumed to be a directory ) is null,  there's no parent, so it's the root ;)
                var fi = new FileInfo(diskBrowerNode.FileHandler.Path);
                return false;
                return fi.Directory == null;
            }
        }

        #endregion

        // a new file was created in the current directory.

        #region Public Methods

        /// <summary>
        /// Navigates to a new node
        /// </summary>
        /// <param name="newNode">The node to navigate to</param>
        public override void GoToNode(IBrowserNodeViewModel newNode)
        {
            if (newNode == null) throw new ArgumentNullException("newNode");
            _currentElement = newNode;

            // use a cast to make sure the File Handler is of the right type. (to use diskBrowserNode.FileHandler.Path)
            var diskBrowserNode = newNode as IBrowserNodeViewModel<IDiskFileHandler>;
            if (diskBrowserNode == null)
            {
                string message = string.Format(
                    "New node of type {0} is not of the right type : {1} expected", 
                    newNode.GetType().Name,
                    typeof(IBrowserNodeViewModel<IDiskFileHandler>).Name);
                throw new ArgumentException(message);
            }

            //FIXME : Handle exceptions !
            //let's monitor the new node
            _fileSystemWatcher.Path = diskBrowserNode.FileHandler.Path;
            onCurrentChildrenChanged(this, new CurrentChildrenChangedEventArgs { Children = getCurrentChildren() });          
        }

        /// <summary>
        /// Goes to parent node.
        /// </summary>
        /// <param name="node">The node to whose parent we need to navigate.</param>
        public override void GoToParentNode(IBrowserNodeViewModel node)
        {
            var diskBrowserNode = node as BrowserNodeViewModel<IDiskFileHandler>;
            if (diskBrowserNode == null)
            {
                string message = string.Format(
                    "New node of type {0} is not of the right type : {1} expected", 
                    node.GetType().Name,
                    typeof(BrowserNodeViewModel<IDiskFileHandler>).Name);

                throw new ArgumentException(message);
            }

            string path = Path.GetDirectoryName(diskBrowserNode.FileHandler.Path);
            if (path== null)
            {
                // Show the drives list.
                var contentType = "diskslist/local";
                IDiskFileHandler fileHandler = _fileHandlerFactory.Create(path, contentType);

                var browserNodeViewModel = _browserNodeViewModelFactory.Create(fileHandler, _mediaHandlerFactoryProvider, _templateProvider);
                GoToNode(browserNodeViewModel);
            }
            else
            {                            
                var contentType = _mediaHandlerFactoryProvider.GetContentTypeFromPath(path);
                IDiskFileHandler fileHandler = _fileHandlerFactory.Create(path, contentType);

                var browserNodeViewModel = _browserNodeViewModelFactory.Create(fileHandler, _mediaHandlerFactoryProvider, _templateProvider);
                GoToNode(browserNodeViewModel);
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Gets the children for the provided Node. 
        /// </summary>
        /// <remarks>For internal use only : Do not call directly. Please, register to the CurrentChildrenChanged event. This method should *always* be run on the UI thread. Use the createChildrenSourceInDispatcher() method to call it from an other thread.</remarks>
        /// <param name="node">The node for which the children will return the children. If set to null, the method will return the children for the current Node (as last set by the GoToNode method).</param>
        /// <returns>The children of the provided node.</returns>
        protected override ObservableCollection<IBrowserNodeViewModel> getChildren(IBrowserNodeViewModel node)
        {
            var diskBrowerNode = node as IBrowserNodeViewModel<IDiskFileHandler>;
            if (diskBrowerNode == null)
            {
                string message = string.Format(
                    "New node of type {0} is not of the right type : {1} expected",
                    node.GetType().Name,
                    typeof(IBrowserNodeViewModel<IDiskFileHandler>).Name);

                throw new ArgumentException(message);
            }
            string path = diskBrowerNode.FileHandler.Path;

            // If we need to list all drives
            // TODO : Remove magic strings
            if (path.Equals("diskslist://root", StringComparison.OrdinalIgnoreCase))
            {
                throw new NotSupportedException("Listing drives not yet supported");
            }
            // get all entries in the folder represented by te current node.
            
            var fsis = new DirectoryInfo(path).GetFileSystemInfos();

            // filter hidden entries and sort them, directories first files then + secondary sort on directory name.
            var orderedFiles = from file in fsis
                               where (!file.Attributes.HasFlag(FileAttributes.Hidden))
                               orderby file.Attributes.HasFlag(FileAttributes.Directory) descending, file.Name ascending
                               select file;
            
            // Note : Let's use a temporary collection to store the elements so that the UI is not refreshed with every single element insertion.
            // Scenario : a folder is displayed, and subfolder is created while displaying its siblings. the newly created folder should appear.
            // Therefore we should bind the UI to the Children collection !
            // if there is a binding (or a INotifyCollectionChanged) on Children : it will be raised a whole bunch of times ! (once for each entry.)
            // Note : this is only if we recreate the whole list every time : If we only make an incremental update to the Children collection, 
            // then only one CollectionChanged event will be raised, but GetCurrentChildren() won't be enough, we'd have to handle this per-element (or per multiple-element subset ) update too! (and that's too complicated ;) )
            var tempChildren = new ObservableCollection<IBrowserNodeViewModel>();
            foreach (var item in orderedFiles)
            {
                string fullName = item.FullName;
                IDiskFileHandler fileHandler = _fileHandlerFactory.Create(fullName, _mediaHandlerFactoryProvider.GetContentTypeFromPath(fullName));

                var childrenBrowserNode = _browserNodeViewModelFactory.Create(fileHandler, _mediaHandlerFactoryProvider, _templateProvider);
                tempChildren.Add(childrenBrowserNode);
            }

            return tempChildren;
        }

        /// <summary>
        /// Gets the children of the current node.
        /// </summary>
        /// <returns></returns>
        protected ObservableCollection<IBrowserNodeViewModel> getCurrentChildren()
        {
            return getChildren(_currentElement);
        }

        /// <summary>
        /// Executes the getCurrentChildren method in the dispatcher provided as an argument. No thread access check nor verification is performed.
        /// </summary>
        /// <param name="dispatcher">The dispatcher running in the thread the getCurrentChildren method should be run in.</param>
        /// <returns></returns>
        private IEnumerable<IBrowserNodeViewModel> createChildrenSourceInDispatcher(Dispatcher dispatcher)
        {
            // no need to CheckAccess because we *know* this will *always* be called from another thread (raised by the FileSystemWatcher !)
            var operation = dispatcher.BeginInvoke(new Func<IEnumerable<IBrowserNodeViewModel>>(getCurrentChildren));
            operation.Wait();
            return (IEnumerable<IBrowserNodeViewModel>)operation.Result;
        }

        private void fileSystemWatcherCreated(object sender, FileSystemEventArgs e)
        {
            // Must create the DependencySource on the same thread as the DependencyObject !
            IEnumerable<IBrowserNodeViewModel> childrenSource = createChildrenSourceInDispatcher(Dispatcher);
            
            onCurrentChildrenChanged(this, new CurrentChildrenChangedEventArgs { Children = childrenSource });
        }

        // a file has been deleted from the current directory.
        void fileSystemWatcherDeleted(object sender, FileSystemEventArgs e)
        {
            // Must create the DependencySource on the same thread as the DependencyObject !
            IEnumerable<IBrowserNodeViewModel> childrenSource = createChildrenSourceInDispatcher(Dispatcher);

            onCurrentChildrenChanged(this, new CurrentChildrenChangedEventArgs { Children = childrenSource });
        }

        #endregion
    }
}
